package com.zh.webcommon.common.param.secure;

import com.zh.webcommon.common.auth.LoginUserHolder;
import com.zh.webcommon.common.exception.OperationDeniedException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.InputStreamSource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;

import java.io.Closeable;
import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static com.zh.webcommon.CommonConst.GSON;
import static com.zh.webcommon.common.param.secure.ParamSignUtil.getSign;
import static com.zh.webcommon.common.param.secure.ParamSignUtil.validateSign;

/**
 * 安全请求
 * 1、接口放重复调用
 * 2、请求参数防篡改
 *
 * @author ZH
 * @date 16:03 2020/6/22
 */
@Slf4j
@Order(4)
@Aspect
public class EnsureSafeAspect {

    private final RedisTemplate<String, Object> redisTemplate;
    private final String appSecret;

    public EnsureSafeAspect(RedisTemplate<String, Object> redisTemplate, String appSecret) {
        log.info("[IdempotentLogAspect] constructor ...");
        this.redisTemplate = redisTemplate;
        this.appSecret = appSecret;
    }

    @Pointcut("@within(org.springframework.web.bind.annotation.RestController) && @annotation(ensureIdempotent)")
    public void idempotentPointcut(EnsureIdempotent ensureIdempotent) {
    }

    @Pointcut("@within(org.springframework.web.bind.annotation.RestController) && @annotation(ensureSafeSign)")
    public void signPointcut(EnsureSafeSign ensureSafeSign) {
    }


    /**
     * 前置通知
     * 记录用户的访问操作
     */
    @Before("signPointcut(ensureSafeSign)")
    public void doBeforeSign(JoinPoint joinPoint, EnsureSafeSign ensureSafeSign) {
        if (isPostOrPutMethod()) {
            //获取两个关键参数：sign、timestamp，以及body数据
            List<String> query = getRequestQuery("sign", "timestamp");
            if (StringUtils.isEmpty(query.get(0))) {
                throw new IllegalArgumentException("缺少sign参数");
            }
            if (StringUtils.isEmpty(query.get(1)) || !StringUtils.isNumeric(query.get(1))) {
                throw new IllegalArgumentException("缺少timestamp参数");
            }

            var sign = query.get(0);
            var timestamp = Long.parseLong(query.get(1));

            Optional<Object> first = getRequestParams(joinPoint)
                    .stream()
                    .filter(e -> !BeanUtils.isSimpleProperty(e.getClass()))
                    .filter(e -> !(e.getClass().isArray() || e instanceof Collection<?>))
                    .findFirst();

            var realSign = first.map(o -> getSign(o, timestamp, appSecret)).orElse("null");

            if (!validateSign(sign, realSign, timestamp)) {
                throw new OperationDeniedException(ensureSafeSign.message());
            }
        }
    }

    /**
     * 前置通知
     * 记录用户的访问操作
     */
    @Before("idempotentPointcut(ensureIdempotent)")
    public void doBeforeIdempotent(JoinPoint joinPoint, EnsureIdempotent ensureIdempotent) {
        if (isPostOrPutMethod()) {
            String reqParams = getRequestParamsStr(joinPoint);

            String str = LoginUserHolder.currentUserId() +":"+ reqParams;
            var unique = String.valueOf(str.hashCode());

            if (!tagFlag(unique)) {
                throw new OperationDeniedException(ensureIdempotent.message());
            }
        }
    }


    /**
     * 添加到防重表
     *
     * @param unique
     */
    private boolean tagFlag(String unique) {
        var uri = getRequestURI();
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(getKey(uri, unique), unique, Duration.ofSeconds(30)));
    }

    /**
     * 获取请求数据，转为json字符串
     *
     * @param joinPoint
     * @return
     */
    private String getRequestParamsStr(JoinPoint joinPoint) {
        List<Object> params = getRequestParams(joinPoint);

        try {
            return GSON.toJson(params);
        } catch (Exception ex) {
            log.warn("格式化Json出错. cause = {}", ex.getMessage());
            return "";
        }
    }

    /**
     * 获取请求数据，转为json字符串
     *
     * @param joinPoint
     * @return
     */
    private List<Object> getRequestParams(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();

        return Arrays.stream(args).map(e -> {
            if (e instanceof ServletRequest req) {
                return req.getParameterMap();
            }
            return e;
        }).filter(e -> !notJson(e)).toList();
    }

    /**
     * 获取请求来自客户端host
     *
     * @return
     */
    private String getRemoteHost() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest().getRemoteHost();
    }

    /**
     * 获取请求的URI
     *
     * @return
     */
    private String getRequestURI() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest().getRequestURI();
    }

    /**
     * 获取请求的?=参数
     *
     * @return
     */
    private List<String> getRequestQuery(String... queryNames) {
        List<String> queryParams = new ArrayList<>(queryNames.length);
        if (queryNames.length == 0) {
            return queryParams;
        }
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

        for (String queryName : queryNames) {
            queryParams.add(request.getParameter(queryName));
        }
        return queryParams;
    }

    /**
     * 获取请求的URI
     *
     * @return
     */
    private boolean isPostOrPutMethod() {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

        String method = request.getMethod();
        return (method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("PUT"));
    }

    private boolean notJson(Object obj) {
        if (obj.getClass().isArray() || obj instanceof List) {
            List<?> objects = obj.getClass().isArray() ? CollectionUtils.arrayToList(obj) : (List<?>) obj;
            if (objects.isEmpty()) {
                return false;
            } else {
                return notJson(objects.get(0));
            }
        }
        return (obj instanceof ServletRequest || obj instanceof ServletResponse || obj instanceof HttpEntity || obj instanceof InputStreamSource || obj instanceof File || obj instanceof Closeable || obj instanceof ResponseBodyEmitter);
    }

    private static String getKey(String requestURI, String unique) {
        return requestURI + "_" + unique;
    }
}